using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using MacomberMap.Common.Logging;
using MacomberMap.Common.Events;
using MacomberMap.Common.Types;

namespace MacomberMap.Common.Components
{
    /// <summary>
    /// This class holds rdf:IDs in a memory optimized format
    /// </summary>
    [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.None)]    
    public class MM_rdfID : IComparable
    {
        #region Internal variables
       

        /// <summary>
        /// The bytes (including header) for an rdf:ID
        /// </summary>
        public byte[] Bytes
        {
            get
            {
                byte[] OutBytes = new byte[rdfID.Length + 1];
                OutBytes[0] = rdfIDType;
                rdfID.CopyTo(OutBytes,1);
                return OutBytes;
            }
        }

        /// <summary>The bytes comprising the rdf:ID</summary>
        protected internal Byte[] rdfID;

        /// <summary>
        /// The type of the rdf:ID
        /// 1-Underline?
        /// 2-GUID?
        /// 4-GUID Uppercase?
        /// 8-GUID starts with a bracket?
        /// 16-GUID contains dashes?
        /// 32-Decimal Precision Number?
        /// 64-String?
        /// 128-Double right-bracket
        /// </summary>
        protected internal Byte rdfIDType = 0;

        /// <summary>The hash code for the rdf:ID</summary>
        private int _HashCode;
        #endregion

        #region ID creation
        /// <summary>
        /// Create an rdf:ID from a byte stream
        /// </summary>
        /// <param name="inBytes">The incoming bytes</param>
        /// <param name="Start">The starting position</param>
        /// <param name="Length">The number of bytes to be read</param>
        public MM_rdfID(byte[] inBytes, int Start, int Length)
        {
            rdfIDType = inBytes[Start ];
            rdfID = new byte[Length - 1];
            for (int a = Start + 1; a < Start + Length; a++)
                rdfID[a - Start - 1] = inBytes[a];

            if ((rdfIDType & 2) == 2)
                _HashCode = new Guid(rdfID).GetHashCode();
            else if ((rdfIDType & 32) == 32)
                using (MemoryStream mS = new MemoryStream(rdfID))
                using (BinaryReader bRd = new BinaryReader(mS))
                    _HashCode = bRd.ReadDecimal().GetHashCode();
            else
                _HashCode = Encoding.ASCII.GetString(rdfID).GetHashCode();
        }

        /// <summary>
        /// Create a new rdf:ID from a GUID
        /// </summary>
        /// <param name="InGuid"></param>
        public MM_rdfID(Guid InGuid)
        {
            SetGuid("_" + InGuid.ToString("B"));
        }


        /// <summary>
        /// Create an rdf:ID from a byte stream
        /// </summary>
        /// <param name="inBytes"></param>
        public MM_rdfID(byte[] inBytes)
        {
            rdfIDType = inBytes[0];
            rdfID = new byte[inBytes.Length - 1];
            for (int a = 1; a < inBytes.Length; a++)
                rdfID[a - 1] = inBytes[a];

            if ((rdfIDType & 2) == 2)
                _HashCode = new Guid(rdfID).GetHashCode();
            else if ((rdfIDType & 32) == 32)
                using (MemoryStream mS = new MemoryStream(rdfID))
                using (BinaryReader bRd = new BinaryReader(mS))
                    _HashCode = bRd.ReadDecimal().GetHashCode();
            else
                _HashCode = Encoding.ASCII.GetString(rdfID).GetHashCode();
        }

        /// <summary>
        /// Create several rdf:IDs from bytes
        /// </summary>
        /// <param name="inBytes"></param>
        /// <returns></returns>
        public static MM_rdfID[] fromBytes(byte[] inBytes)
        {
            List<MM_rdfID> OutIDs = new List<MM_rdfID>();
            int CurPos = 0;
            while (CurPos < inBytes.Length)
            {
                if (((inBytes[CurPos+1] & 2) == 2) || ((inBytes[CurPos+1] & 32) == 32))
                {
                    OutIDs.Add(new MM_rdfID(inBytes, CurPos, 18));
                    CurPos += 18;
                }
                else
                {
                    int EndString = CurPos + 2;
                    while (inBytes[EndString] != 0)
                        EndString++;
                    OutIDs.Add(new MM_rdfID(inBytes, CurPos, EndString - CurPos + 1));
                    CurPos = EndString + 1;
                }
            }
            return OutIDs.ToArray();
        }
            
        
        /// <summary>
        /// Create a new CIM:ID from an rdf:ID
        /// </summary>
        /// <param name="rdfID">The rdf:ID to optimize</param>
        public MM_rdfID(String rdfID)
        {

            //Determine whether it contains an underscore as the first character
            if (rdfID.Length == 0)
            {
                rdfID = "";
                this.rdfIDType = 1;
            }
            else if (rdfID[0] == '_')
            {
                rdfID = rdfID.Substring(1);
                this.rdfIDType = 1;
            }
            else
                this.rdfIDType = 0;

            //Determine whether the rdf:ID is a GUID, number or string
            bool WasGuid = false;
            if (rdfID.Length > 0 && rdfID[0] == '{')
                WasGuid = SetGuid(rdfID);
            else if ((rdfID.Length == 36) && (rdfID[8] == '-') && (rdfID[13] == '-') && (rdfID[18] == '-') && (rdfID[23] == '-'))
                WasGuid = SetGuid(rdfID);

            if (!WasGuid && IsAllNumbers(rdfID))
                SetNumber(rdfID);
            else if (!WasGuid)
                SetString(rdfID);
        }


        /// <summary>
        /// Determine whether an rdf:ID consists of only numbers
        /// </summary>
        /// <param name="inString"></param>
        /// <returns></returns>
        private bool IsAllNumbers(String inString)
        {
            if (inString.Length == 0)
                return false;
            else
                foreach (Char c in inString)
                    if (!char.IsNumber(c))
                        return false;
            return true;
        }

        /// <summary>
        /// Set the current rdf:ID instance as a number
        /// </summary>
        /// <param name="inString">The rdf:ID to store as a number</param>
        private void SetNumber(String inString)
        {
            using (MemoryStream m = new MemoryStream(16))
            using (BinaryWriter w = new BinaryWriter(m))
            {
                Decimal NewNum = Decimal.Parse(inString);
                w.Write(NewNum);
                this.rdfIDType += 32;
                this.rdfID = m.GetBuffer();
                this._HashCode = NewNum.GetHashCode();
            }
        }

        /// <summary>
        /// Set the current rdf:ID instance as a string
        /// </summary>
        /// <param name="inString">The rdf:ID to store as a string</param>
        private void SetString(String inString)
        {
            this.rdfIDType += 64;
            this.rdfID = Encoding.ASCII.GetBytes(inString + "\0");
            this._HashCode = inString.GetHashCode();
        }


        /// <summary>
        /// Set the current rdf:ID instance as a GUID
        /// </summary>
        /// <param name="inGuid">The rdf:ID to store as a GUID</param>        
        private bool SetGuid(String inGuid)
        {
            try
            {
                Guid NewGuid = new Guid(inGuid.Trim('{', '}'));
               
                //Determine whether the rdf:ID contains uppercase or lowercase hexadecimal characters
                bool isUpper = true;
                foreach (Char c in inGuid.ToCharArray())
                    if (Char.IsLower(c))
                    { isUpper = false; break; }

                rdfIDType += 2;
                if (inGuid.Contains("-"))
                    rdfIDType += 16;
                if (inGuid[0] == '{')
                    rdfIDType += 8;
                if (inGuid.EndsWith("}}"))
                    rdfIDType += 128;
                if (isUpper)
                    rdfIDType += 4;

                //Now store the GUID as a byte array
                rdfID = new byte[16];

                NewGuid.ToByteArray().CopyTo(rdfID, 0);
                this._HashCode = NewGuid.GetHashCode();
                return true;
            }
            catch (Exception ex)
            {
                MM_Log.LogEvent(new MM_Event_InvalidGUID(inGuid, this, ex)); return false;
            }
        }


        #endregion

        #region Public members
        /// <summary>
        /// Return the hash code for this rdf:ID
        /// </summary>
        /// <returns>Hash code</returns>
        public override int GetHashCode()
        {
            return _HashCode;
        }


        /// <summary>
        /// Return the string that should match the CIM Element's rdf:ID.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            StringBuilder OutString = new StringBuilder();
            if ((rdfIDType & 1) == 1)
                OutString.Append("_");

            //If we have a decimal-precision number, write it out
            if ((rdfIDType & 32) == 32)
                using (MemoryStream m = new MemoryStream(rdfID))
                using (BinaryReader b = new BinaryReader(m))
                    OutString.Append(b.ReadDecimal().ToString());

                //If we have a string, write it out
            else if ((rdfIDType & 64) == 64)
                OutString.Append(Encoding.ASCII.GetString(rdfID, 0, rdfID.Length - 1));

            //If we have a GUID, write it out.
            else if ((rdfIDType & 2) == 2)
            {
                //If we have a bracket, write it.
                if ((rdfIDType & 8) == 8)
                    OutString.Append('{');

                //Write out our GUID
                if ((rdfIDType & 4) == 4)
                    OutString.Append(new Guid(rdfID).ToString((((rdfIDType & 16) == 16) ? "D" : "N")).ToUpper());
                else
                    OutString.Append(new Guid(rdfID).ToString((((rdfIDType & 16) == 16) ? "D" : "N")).ToLower());

                //Now, close the bracket if necessary
                if ((rdfIDType & 8) == 8)
                    OutString.Append('}');

                //Add the double-bracket as needed
                if ((rdfIDType & 128) == 128)
                    OutString.Append('}');

            }
            return OutString.ToString();
        }

        #endregion

        #region IComparable Members

        /// <summary>
        /// Determine if two rdf:IDs are identical
        /// </summary>
        /// <param name="obj">The rdf:ID to which we should be comparing</param>
        /// <returns></returns>
        public int CompareTo(object obj)
        {
            if (obj is MM_rdfID == false)
                throw new Exception("The method or operation is not implemented.");
            MM_rdfID that = (MM_rdfID)obj;
            for (int a = 0; a < Math.Min(this.rdfID.Length, that.rdfID.Length); a++)
                if (this.rdfID[a] > that.rdfID[a])
                    return 1;
                else if (this.rdfID[a] < that.rdfID[a])
                    return -1;
            return this.rdfID.Length.CompareTo(that.rdfID.Length);
        }

        /// <summary>
        /// Determine if two rdf:IDs are identical
        /// </summary>
        /// <param name="obj">The rdf:ID to which we should be comparing</param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            if (obj is MM_rdfID == false)
                throw new Exception("The method or operation is not implemented.");
            MM_rdfID that = (MM_rdfID)obj;
            if (this.rdfIDType != that.rdfIDType || this.rdfID.Length != that.rdfID.Length)
                return false;
            for (int a = 0; a < this.rdfID.Length; a++)
                if (this.rdfID[a] != that.rdfID[a])
                    return false;
            return true;
        }
        #endregion

       
    }


    #region rdf:ID Comparison
    /// <summary>
    /// This class is responsible for optimally comparing two rdf:IDs to determine whether they are equal.
    /// </summary>
    class MM_rdfID_Comparer : IEqualityComparer<MM_rdfID>
    {
        #region IEqualityComparer<MM_rdfID> Members

        /// <summary>
        /// Determines whether two rdf:IDs are equal
        /// </summary>
        /// <param name="x">rdf:ID 1</param>
        /// <param name="y">rdf:ID 2</param>
        /// <returns>Equality boolean</returns>
        public bool Equals(MM_rdfID x, MM_rdfID y)
        {
            if (x.rdfIDType != y.rdfIDType)
                return false;
            else if (x.rdfID.Length != y.rdfID.Length)
                return false;
            else
                for (int a = 1; a < x.rdfID.Length; a++)
                    if (x.rdfID[a] != y.rdfID[a])
                        return false;
            return true;
        }

        /// <summary>
        /// Return the hash code for an rdf:ID
        /// </summary>
        /// <param name="rdfID">The rdfID to return the hash code for</param>
        /// <returns>Hash code</returns>
        public int GetHashCode(MM_rdfID rdfID)
        {
            return rdfID.GetHashCode();
        }

        #endregion
    }
    #endregion
}